/////////////////////////////////////////////////////////////////////////////
//
// (c) 2007 BinaryComponents Ltd.  All Rights Reserved.
//
// http://www.binarycomponents.com/
//
/////////////////////////////////////////////////////////////////////////////

using System;
using System.Drawing;
using System.Windows.Forms;

namespace SuperListEx
{
	public class Column
    {
        #region vars
        private string _name;
        private string _caption;
        private int _width;
        private bool _isGrouped = false;
        private bool _isVisible = true;
        private SortOrder _sortOrder = SortOrder.None;
        private SortOrder _groupSortOrder = SortOrder.Ascending;
        private ColumnItemValueAccessor _groupItemValueAccessor = null;
        #endregion

        #region Properties
        public object AccessorParameter 
        { 
            get;
            set; 
        }

        public int ColumnHeaderIndex
        {
            get;
            set;
        }

        public ListControl Parent
        {
            get;
            set;
        }

        public Type CellType
        {
            get;
            set;
        }

        /// <summary>
        /// Comparitor for a column item. Note it may be called by
        /// separate threads.
        /// </summary>
        /// <returns></returns>
        public Comparison<object> Comparitor
        {
            get;
            set;
        }

        /// <summary>
        /// Delegate to get the value of an item
        /// </summary>
        public ColumnItemValueAccessor ColumnItemFormattedAccessor
        {
            get;
            set;
        }

        /// <summary>
        /// Delegate to convert an item into a string given this column
        /// </summary>
        public ColumnItemValueAccessor ColumnItemAccessor
        {
            get;
            set;
        }

        public ColumnItemValueAccessorWithParameter ColumnItemAccessorWithParameter
        {
            get;
            set;
        }

        public ColumnItemValueAccessorWithParameter GroupItemAccessorWithParameter
        {
            get;
            set;
        }

        /// <summary>
        /// Comparitor for a grouped column item. Note it may be called by
        /// separate threads. Default is to use Comparitor
        /// </summary>
        /// <returns></returns>
        public Comparison<object> GroupedComparitor
        {
            get;
            set;
        }

        /// <summary>
        /// Is fixed width column header
        /// </summary>
        public bool IsFixedWidth
        {
            get;
            set;
        }

        /// <summary>
        /// Warp the text to fit inside the column width
        /// </summary>
        public bool WrapText
        {
            get;
            set;
        }

        /// <summary>
        /// Header icon
        /// </summary>
        public Icon HeaderIcon
        {
            get;
            set;
        }

        /// <summary>
        /// Show the column header sort arrow.
        /// </summary>
        public bool ShowHeaderSortArrow
        {
            get;
            set;
        }

        /// <summary>
        /// Delegate that's called when the group value of an item is needed. Default behaviour if not set is
        /// to call the ColumnItemAccessor.
        /// </summary>
        public ColumnItemValueAccessor GroupItemAccessor
        {
            get
            {
                return _groupItemValueAccessor == null ? ColumnItemFormattedAccessor == null ? ColumnItemAccessor : ColumnItemFormattedAccessor : _groupItemValueAccessor;
            }
            set
            {
                _groupItemValueAccessor = value;
            }
        }

        /// <summary>
        /// When a column is dragged to the group area this property denotes what to do,
        /// either make a copy of the column or move it. The default behaviour is to move.
        /// </summary>
        public MoveToGroupBehaviour MoveBehaviour
        {
            get;
            set;
        }

        /// <summary>
        /// The name of the column.
        /// </summary>
        public string Name
        {
            get
            {
                return _name;
            }
            set
            {
                if (_name != value)
                {
                    _name = value;
                    OnDataChanged(WhatPropertyChanged.Name);
                }
            }
        }

        /// <summary>
        /// Current sort order of the column
        /// </summary>
        public SortOrder SortOrder
        {
            get
            {
                return _sortOrder;
            }
            set
            {
                if (_sortOrder != value)
                {
                    _sortOrder = value;
                    OnDataChanged(WhatPropertyChanged.SortOrder);
                }
            }
        }

        /// <summary>
        /// Grouped sort order of the column.
        /// </summary>
        public SortOrder GroupSortOrder
        {
            get
            {
                return _groupSortOrder;
            }
            set
            {
                if (_groupSortOrder != value)
                {
                    _groupSortOrder = value == null ? SortOrder.Descending : value;
                    OnDataChanged(WhatPropertyChanged.SortOrder);
                }
            }
        }

        /// <summary>
        /// Actual text used to display in the column
        /// </summary>
        public string Caption
        {
            get
            {
                return _caption;
            }
            set
            {
                if (_caption != value)
                {
                    _caption = value;
                    OnDataChanged(WhatPropertyChanged.Caption);
                }
            }
        }

        /// <summary>
        /// Width of the column
        /// </summary>
        public int Width
        {
            get
            {
                return _width;
            }
            set
            {
                if (_width != value)
                {
                    _width = value;
                    OnDataChanged(WhatPropertyChanged.Width);
                }
            }
        }

        /// <summary>
        /// Is the column visible on the control.
        /// </summary>
        public bool IsVisible
        {
            get
            {
                return _isVisible;
            }
            set
            {
                if (_isVisible != value)
                {
                    _isVisible = value;
                    OnDataChanged(WhatPropertyChanged.IsVisible);
                }
            }
        }

        /// <summary>
        /// Is the column part of grouping area within the control.
        /// </summary>
        public bool IsGrouped
        {
            get
            {
                return _isGrouped;
            }
            set
            {
                if (_isGrouped != value)
                {
                    _isGrouped = value;
                    OnDataChanged(WhatPropertyChanged.IsGroup);
                }
            }
        }

        /// <summary>
        /// Alignment of the column text within the column header.
        /// </summary>
        public Alignment Alignment
        {
            get;
            set;
        }

        /// <summary>
        /// Vertical alignment of the column text within the column header.
        /// </summary>
        public VerticalAlignment VerticalAlignment
        {
            get;
            set;
        }
        #endregion

        #region Events
        /// <summary>
        /// Event that gets fired when something in the column changes.
        /// </summary>
        public event ColumnDataChangedHandler DataChanged;
        #endregion

        #region Constructor
        private Column()
        {
            MoveBehaviour = MoveToGroupBehaviour.Move;
            Alignment = Alignment.Left;
            VerticalAlignment = VerticalAlignment.Center;
            IsFixedWidth = false;
            ShowHeaderSortArrow = true;
            WrapText = false;
            ColumnHeaderIndex = 0;
        }

        /// <summary>
		/// Constructs a basic column
		/// </summary>
		/// <param name="name">Name of column, this can be used to identify the column across languages being used where caption will be different</param>
		/// <param name="caption">The caption used on the header.</param>
		/// <param name="width">Initial width of the column</param>
		public Column( string name, string caption, int width, ColumnItemValueAccessor columnItemValueAccessor )
            : this()
		{
			if( name == null )
			{
				throw new ArgumentNullException( "name" );
			}
			if( caption == null )
			{
				throw new ArgumentNullException( "caption" );
			}
			if( width < 0 )
			{
				throw new ArgumentOutOfRangeException( "width" );
			}

			_name = name;
			_caption = caption;
			_width = width;

            ColumnItemAccessorWithParameter = null;
            ColumnItemAccessor = columnItemValueAccessor;
            AccessorParameter = null;

            Comparitor = ColumnItemComparer;
            GroupedComparitor = GroupItemComparer;
		}

        public Column(string name, string caption, int width, object parameter, ColumnItemValueAccessorWithParameter columnItemValueAccessorWithParameter)
            : this()
        {
            if (name == null)
            {
                throw new ArgumentNullException("name");
            }
            if (caption == null)
            {
                throw new ArgumentNullException("caption");
            }
            if (width < 0)
            {
                throw new ArgumentOutOfRangeException("width");
            }

            _name = name;
            _caption = caption;
            _width = width;

            ColumnItemAccessorWithParameter = columnItemValueAccessorWithParameter;
            ColumnItemAccessor = null;
            AccessorParameter = parameter;

            Comparitor = ColumnItemComparer;
            GroupedComparitor = GroupItemComparer;
        }

        /// <summary>
        /// Creates a copy of the given column
        /// </summary>
        /// <param name="columnToCopy"></param>
        public Column(Column columnToCopy)
            : this()
        {
            _name = columnToCopy._name;
            Comparitor = columnToCopy.Comparitor;
            _caption = columnToCopy._caption;
            _width = columnToCopy._width;
            _sortOrder = columnToCopy._sortOrder;
            _groupSortOrder = columnToCopy._groupSortOrder;
            _isVisible = columnToCopy._isVisible;
            _isGrouped = columnToCopy._isGrouped;
            ColumnItemAccessor = columnToCopy.ColumnItemAccessor;
            ColumnItemAccessorWithParameter = columnToCopy.ColumnItemAccessorWithParameter;
            GroupItemAccessorWithParameter = columnToCopy.GroupItemAccessorWithParameter;
            _groupItemValueAccessor = columnToCopy._groupItemValueAccessor;
            GroupedComparitor = columnToCopy.GroupedComparitor;
            HeaderIcon = columnToCopy.HeaderIcon;
            IsFixedWidth = columnToCopy.IsFixedWidth;
            ShowHeaderSortArrow = columnToCopy.ShowHeaderSortArrow;
            WrapText = columnToCopy.WrapText;
        }
        #endregion

        #region Object overrides
        public override string ToString()
		{
			return string.Format( "SuperList Column: '{0}'", Name );
        }
        #endregion

        #region Implementation
        public virtual int ColumnItemComparer(object x, object y)
        {
            object cellItemX = null;
            object cellItemY = null;

            if (ColumnItemAccessor != null)
            {
                cellItemX = ColumnItemAccessor(x);
                cellItemY = ColumnItemAccessor(y);
            }
            else if (ColumnItemAccessorWithParameter != null)
            {
                cellItemX = ColumnItemAccessorWithParameter(x, AccessorParameter);
                cellItemY = ColumnItemAccessorWithParameter(y, AccessorParameter);
            }

            IComparable comparer = cellItemX as IComparable;
            if (comparer == null)
            {
                string error = string.Format("Cell item for column '{0}' doesn't support comparing. You will need to supply a comparer by setting Column.Comparitor", Name);
                throw new NotSupportedException(error);
            }

            if(cellItemX.GetType().Equals(cellItemY.GetType()))
                return comparer.CompareTo(cellItemY);
            else
                return comparer.GetHashCode().CompareTo(cellItemY.GetHashCode());
        }

        public virtual int GroupItemComparer(object x, object y)
        {
            if (x == y)
            {
                return 0;
            }

            if (x != null && y != null)
            {
                if (GroupItemAccessorWithParameter != null)
                {
                    if (GroupItemAccessorWithParameter(x, AccessorParameter) == GroupItemAccessorWithParameter(y, AccessorParameter))
                    {
                        return 0;
                    }
                }
                else
                {
                    if (GroupItemAccessor != null)
                    {
                        if (GroupItemAccessor(x) == GroupItemAccessor(y))
                        {
                            return 0;
                        }
                    }
                    else if (ColumnItemAccessorWithParameter != null)
                    {
                        if (ColumnItemAccessorWithParameter(x, AccessorParameter) == ColumnItemAccessorWithParameter(y, AccessorParameter))
                        {
                            return 0;
                        }
                    }
                }
            }

            return Comparitor(x, y);
        }

        /// <summary>
		/// Called to fire a data changed event.
		/// </summary>
		/// <param name="whatChanged"></param>
		protected virtual void OnDataChanged( WhatPropertyChanged whatChanged )
		{
			if( DataChanged != null )
			{
				DataChanged( this, new ColumnDataChangedEventArgs( this, whatChanged ) );
			}
        }
        #endregion
    }
}